package com.lyc.api;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.lyc.constants.Constants;
import com.lyc.utils.StringUtil;
import com.lyc.utils.bd.SignatureUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.HttpClientBuilder;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.*;

/**
 * 百度广告API客户端
 */
public class BdApiClient {

    private static final Gson gson = new Gson();
    public static String DB_APP_SID;
    public static String BD_ACCESS_KEY;
    private static final String DB_PRIVATE_KEY_FILE = Constants.ACCOUNT_PATH + "privkey.pem";
    private static PrivateKey privateKey;
    private static final String HOST = "https://ubapi.baidu.com";

    static {
        loadProperties();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        loadPrivateKey();
    }

    private static void loadProperties() {
        Properties props = new Properties();
        try (FileInputStream fis = new FileInputStream(new File(Constants.ACCOUNT_PATH + "account.properties"))) {
            props.load(fis);

            DB_APP_SID = String.valueOf(props.getProperty("DB_APP_SID"));
            BD_ACCESS_KEY = String.valueOf(props.getProperty("BD_ACCESS_KEY"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void loadPrivateKey() {
        try (InputStream inputStream = new FileInputStream(DB_PRIVATE_KEY_FILE)) {
            String privateKeyStr = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            privateKey = SignatureUtils.parsePEMPrivateKey(privateKeyStr);
        } catch (IOException e) {
            throw new RuntimeException("加载百度私钥失败", e);
        }
    }

    /**
     * 创建激励视频广告
     *
     * @return 广告ID
     * @throws IOException 如果请求失败
     */
    public static Integer createBdRewardAd() throws IOException {
        Map<String, Object> adInfo = new HashMap<>();
        adInfo.put("reward_video_return_control", 1);
        adInfo.put("reward_video_return_url", "http://quanminchangwan.kuhuanqiye.top/api/huitiao/houtai/baidu");
        adInfo.put("reward_video_voice_control", 1);

        return createBdAd(44, "百度_激励_rv", adInfo);
    }

    /**
     * 创建信息流广告
     *
     * @return 广告ID
     * @throws IOException 如果请求失败
     */
    public static Integer createBdNativeAd() throws IOException {
        Map<String, Object> adInfo = new HashMap<>();
        adInfo.put("info_flow_style_control_type", 1);
        adInfo.put("info_flow_material_types", List.of(1, 7));

        return createBdAd(36, "百度_信息流_rv", adInfo);
    }

    /**
     * 创建插屏广告
     *
     * @return 广告ID
     * @throws IOException 如果请求失败
     */
    public static Integer createBdInsertAd() throws IOException {
        Map<String, Object> adInfo = new HashMap<>();
        adInfo.put("interstitial_ad_scene", 1);
        adInfo.put("interstitial_style_types", List.of(1, 2, 4, 5));

        return createBdAd(34, "百度_插屏_rv", adInfo);
    }

    /**
     * 创建开屏广告
     *
     * @return 广告ID
     * @throws IOException 如果请求失败
     */
    public static Integer createBdSplashAd() throws IOException {
        return createBdAd(33, "百度_开屏_rv", null);
    }

    /**
     * 创建广告位的方法
     *
     * @param adType 广告类型，用于指定创建的广告的类别
     * @param adName 广告名称，用于标识广告的名称
     * @param adInfo 广告的附加信息，可以包含广告的额外属性
     * @return 返回创建的广告位的ID
     * @throws IOException 当网络请求发生错误时抛出此异常
     */
    private static Integer createBdAd(int adType, String adName, Map<String, Object> adInfo) throws IOException {
        String uri = "/ssp/1/sspservice/appadpos/app/adpos/create";

        Map<String, Object> data = new HashMap<>();
        data.put("app_sid", DB_APP_SID);
        data.put("ad_type", adType);
        data.put("ad_name", adName + "_" + StringUtil.generateRandomString(10));
        data.put("price_type", 2);

        if (adInfo != null) {
            data.put("ad_info", adInfo);
        }

        String body = gson.toJson(data);
        // System.out.println("body: \n" + body);

        HttpResponse httpResponse = post(uri, "application/json", body.getBytes(StandardCharsets.UTF_8));

        return parseResponse(httpResponse);
    }

    /**
     * 解析HTTP响应并返回特定数据
     *
     * @param httpResponse HTTP响应对象，包含服务器的响应
     * @return 返回从响应中解析出的整数，表示特定数据；如果请求失败或解析异常，返回-1
     * @throws IOException 如果在读取响应内容时发生I/O错误
     */
    private static Integer parseResponse(HttpResponse httpResponse) throws IOException {
        InputStream inputStream = httpResponse.getEntity().getContent();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int length;

        while ((length = inputStream.read(buffer)) > -1) {
            outputStream.write(buffer, 0, length);
        }

        Type responseType = new TypeToken<Map<String, Object>>() {}.getType();
        Map<String, Object> responseMap = gson.fromJson(outputStream.toString(StandardCharsets.UTF_8), responseType);

        System.out.println("responseMap = " + responseMap);

        int code = ((Number) responseMap.get("code")).intValue();

        if (code == 0) {
            Map<String, Object> adUnitListResponse = (Map<String, Object>) responseMap.get("data");
            Number dataTuId = (Number) adUnitListResponse.get("tu_id");
            return dataTuId.intValue();
        } else {
            System.out.println("请求失败。 Code: " + code);
            return -1;
        }
    }

    /**
     * 发送GET请求
     *
     * @param uri 请求URI
     * @return HTTP响应
     * @throws IOException 如果请求失败
     */
    public HttpResponse get(String uri) throws IOException {
        return execute("GET", uri, null, null);
    }

    /**
     * 发送POST请求
     *
     * @param uri        请求URI
     * @param contentType 内容类型
     * @param content    请求内容
     * @return HTTP响应
     * @throws IOException 如果请求失败
     */
    public static HttpResponse post(String uri, String contentType, byte[] content) throws IOException {
        return execute("POST", uri, contentType, content);
    }

    /**
     * 发送带有文件的POST请求
     *
     * @param uri   请求URI
     * @param file  文件
     * @return HTTP响应
     * @throws IOException 如果请求失败
     */
    public HttpResponse post(String uri, File file) throws IOException {
        return execute("POST", uri, file);
    }

    /**
     * 发送PUT请求
     *
     * @param uri        请求URI
     * @param contentType 内容类型
     * @param content    请求内容
     * @return HTTP响应
     * @throws IOException 如果请求失败
     */
    public HttpResponse put(String uri, String contentType, byte[] content) throws IOException {
        return execute("PUT", uri, contentType, content);
    }

    /**
     * 执行HTTP请求
     *
     * @param method HTTP方法，如GET、POST等
     * @param uri 请求的统一资源标识符
     * @param contentType 请求内容的类型
     * @param content 请求的内容，以字节数组形式表示
     * @return 返回HTTP响应
     * @throws IOException 当请求过程中发生I/O错误时抛出此异常
     */
    private static HttpResponse execute(String method, String uri, String contentType, byte[] content) throws IOException {
        long timeStamp = System.currentTimeMillis();

        List<String> itemsToBeSigned = Arrays.asList(
                BD_ACCESS_KEY,
                method,
                uri,
                Long.toString(timeStamp),
                contentType == null ? "" : contentType,
                content == null ? "" : md5(content)
        );

        String stringToBeSigned = StringUtils.join(itemsToBeSigned, "\n");
        byte[] signatureBytes = SignatureUtils.signMessage(privateKey, stringToBeSigned.getBytes(StandardCharsets.UTF_8));
        String signature = new String(Base64.encode(signatureBytes));

        RequestBuilder requestBuilder = RequestBuilder.create(method);
        RequestConfig config = RequestConfig.custom()
                .setSocketTimeout(60000)
                .setConnectTimeout(30000)
                .setConnectionRequestTimeout(5000)
                .build();

        requestBuilder.setConfig(config)
                .setUri(HOST + uri)
                .setVersion(HttpVersion.HTTP_1_1)
                .addHeader("x-ub-date", Long.toString(timeStamp))
                .addHeader("x-ub-authorization", BD_ACCESS_KEY + ":" + signature);

        if (contentType != null && content != null) {
            requestBuilder.addHeader("Content-Type", contentType)
                    .setEntity(new ByteArrayEntity(content));
        }

        return HttpClientBuilder.create().build().execute(requestBuilder.build());
    }

    /**
     * 执行HTTP请求
     *
     * @param method HTTP方法，如GET、POST等
     * @param uri 请求的URI
     * @param file 要上传的文件
     * @return HttpResponse对象，包含响应信息
     * @throws IOException 如果发生I/O错误
     */
    private static HttpResponse execute(String method, String uri, File file) throws IOException {
        long timeStamp = System.currentTimeMillis();

        MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create().addPart("file", new FileBody(file));
        HttpEntity reqEntity = entityBuilder.build();
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        reqEntity.writeTo(outStream);
        byte[] bytes = outStream.toByteArray();

        List<String> itemsToBeSigned = Arrays.asList(
                BD_ACCESS_KEY,
                method,
                uri,
                Long.toString(timeStamp),
                reqEntity.getContentType().getValue(),
                md5(bytes)
        );

        String stringToBeSigned = StringUtils.join(itemsToBeSigned, "\n");
        byte[] signatureBytes = SignatureUtils.signMessage(privateKey, stringToBeSigned.getBytes(StandardCharsets.UTF_8));
        String signature = new String(Base64.encode(signatureBytes));

        RequestBuilder requestBuilder = RequestBuilder.create(method);
        RequestConfig config = RequestConfig.custom()
                .setSocketTimeout(600000)
                .setConnectTimeout(30000)
                .setConnectionRequestTimeout(5000)
                .build();

        requestBuilder.setConfig(config)
                .setUri(HOST + uri)
                .setVersion(HttpVersion.HTTP_1_1)
                .addHeader("x-ub-date", Long.toString(timeStamp))
                .addHeader("x-ub-authorization", BD_ACCESS_KEY + ":" + signature)
                .setEntity(reqEntity);

        return HttpClientBuilder.create().build().execute(requestBuilder.build());
    }

    /**
     * 使用MD5算法对给定内容进行哈希
     * 此方法将输入的内容转换为MD5哈希值，以字符串形式返回
     * 主要步骤包括：初始化MD5Digest对象、更新对象状态、完成哈希计算并返回结果
     *
     * @param content 要进行哈希处理的字节数组
     * @return 字符串形式的MD5哈希值
     */
    private static String md5(byte[] content) {
        MD5Digest digest = new MD5Digest();
        digest.update(content, 0, content.length);
        byte[] digestBytes = new byte[digest.getDigestSize()];
        digest.doFinal(digestBytes, 0);
        return new String(Hex.encode(digestBytes));
    }
}
